2.3 常量
常量表示运行时恒定不可改变的值,通常是一些字面量。使用常量就可用一个易于阅读理解的标识符号来代替“魔法数字”,也使得在调整常量值时,无须修改所有引用代码。
常量值必须是编译期可确定的字符、字符串、数字或布尔值。可指定常量类型,或由编译器通过初始化值推断,不支持C/C++数字类型后缀。
const x,y int=123,0x22
const s= "hello,world!"
const c= '我' //rune(unicode code point)
const(
i,f=1,0.123 //int,float64(默认)
b =false
)可在函数代码块中定义常量,不曾使用的常量不会引发编译错误。
func main() {
const x=123
println(x)
const y=1.23 // 未使用,不会引发编译错误
{
const x= "abc" // 在不同作用域定义同名常量
println(x)
}
}
输出:
123 abc
如果显式指定类型,必须确保常量左右值类型一致,需要时可做显式转换。右值不能超出常量类型取值范围,否则会引发溢出错误。
const(
x,y int =99, -999
b byte =byte(x) //x被指定为int类型,须显式转换为byte类型
n =uint8(y) // 错误:constant-999 overflows uint8
)常量值也可以是某些编译器能计算出结果的表达式,如unsafe.Sizeof、len、cap等。
import"unsafe"
const(
ptrSize=unsafe.Sizeof(uintptr(0))
strSize=len("hello,world!")
)
在常量组中如不指定类型和初始化值,则与上一行非空常量右值(表达式文本)相同。
import "fmt"
func main() {
const(
x uint16=120
y // 与上一行x类型、右值相同
s = "abc"
z // 与s类型、右值相同
)
fmt.Printf("%T, %v\n",y,y) // 输出类型和值
fmt.Printf("%T, %v\n",z,z)
}
输出:
uint16,120 string,abc
枚举
Go并没有明确意义上的enum定义,不过可借助iota标识符实现一组自增常量值来实现枚举类型。
const(
x = iota //0
y //1
z //2
)
const(
_ = iota //0
KB = 1 << (10*iota) //1<< (10*1)
MB //1<< (10*2)
GB //1<< (10*3)
)自增作用范围为常量组。可在多常量定义中使用多个iota,它们各自单独计数,只须确保组中每行常量的列数量相同即可。
const(
_, _ = iota,iota*10 //0,0*10
a,b //1,1*10
c,d //2,2*10
)如中断iota自增,则必须显式恢复。且后续自增值按行序递增,而非C enum那般按上一取值递增。
const(
a =iota //0
b //1
c =100 //100
d //100(与上一行常量右值表达式相同)
e =iota //4(恢复iota自增,计数包括c、d)
f //5
)自增默认数据类型为int,可显式指定类型。
const(
a =iota //int
b float32 = iota //float32
c =iota //int(如不显式指定iota,则与b数据类型相同)
)在实际编码中,建议用自定义类型实现用途明确的枚举类型。但这并不能将取值范围限定在预定义的枚举值内。
type color byte // 自定义类型
const(
black color=iota // 指定常量类型
red
blue
)
func test(c color) {
println(c)
}
func main() {
test(red)
test(100) //100并未超出color/byte类型取值范围
x:=2
test(x) // 错误:cannot use x(type int)as type color in argument to test
}展开
常量除“只读”外,和变量究竟有什么不同?
var x=0x100
const y=0x200
func main() {
println(&x,x)
println(&y,y) // 错误:cannot take the address of y
}不同于变量在运行期分配存储内存(非优化状态),常量通常会被编译器在预处理阶段直接展开,作为指令数据使用。
const y=0x200
func main() {
println(y)
}
输出:
$go build&&go tool objdump-s"main\.main"test
TEXT main.main(SB)test.go
MOVQ$0x200,0(SP) // 将常量值作为指令数据展开
CALL runtime.printint(SB)
数字常量不会分配存储空间,无须像变量那样通过内存寻址来取值,因此无法获取地址。
鉴于Go当前对动态库的支持还不完善,是否存在“常量陷阱”问题,尚有待观察。
提到常量展开,我们还须回头看看常量的两种状态对编译器的影响。
const x=100 // 无类型声明的常量
const y byte=x // 直接展开x,相当于const y byte=100
const a int=100 // 显式指定常量类型,编译器会做强类型检查
const b byte=a // 错误:cannot use a(type int)as type byte in const initializer